/***************************************************************************
 *   mgpi_g3dmdl.c - 3D Graphics Definitions                               *
 *   Copyright (C) 2008 by cmaster.matso                                   *
 *   cmaster.matso@gmail.com                                               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "mgpi_g3dmdl.h"
#include "mgpi_roeskva.h"

cam_t g_aSceneCameras[MGPI_MAX_SCENE_CAMERAS];                                  /** array of scene cameras */
int g_nHandleBase = 1000, g_nCurrentCameraHandle = 0;                           /** (?) */

GLfloat *g_fModelView = NULL;                                                   /** model-view injection matrix */
GLfloat *g_fProjection = NULL;                                                  /** projection injection matrix */

static float4x4 s_aWorld = MGPI_IDENTITY;                                       /** world transformation matrix */
static float4x4 s_aView = MGPI_IDENTITY;                                        /** view transformation matrix */
static float4x4 s_aProjection = MGPI_IDENTITY;                                  /** projection transformation matrix */

static float4 s_aVector = MGPI_DEFAULT_VECTOR;                                  /** transformed vector */
static int s_nMatrix = MGPI_WORLD;                                              /** transformation type flag */

static float s_nXnd = 0.0f, s_nYnd = 0.0f, s_nXw = 0.0f, s_nYw = 0.0f; /* wtf is that??? (?) 0_o */

static GLuint s_nPBOid = 0;														/** texture transfer pixel buffer ID */
static float s_fDepthScale = 1.0f;

/*
static void s_debug_printWorldMatrix(void) {

    printf("s_aWorld = {\n"
            "\t {%2.2f, %2.2f, %2.2f, %2.2f}\n"
            "\t {%2.2f, %2.2f, %2.2f, %2.2f}\n"
            "\t {%2.2f, %2.2f, %2.2f, %2.2f}\n"
            "\t {%2.2f, %2.2f, %2.2f, %2.2f}\n"
            "}\n",
            s_aWorld[0][0], s_aWorld[0][1], s_aWorld[0][2], s_aWorld[0][3],
            s_aWorld[1][0], s_aWorld[1][1], s_aWorld[1][2], s_aWorld[1][3],
            s_aWorld[2][0], s_aWorld[2][1], s_aWorld[2][2], s_aWorld[2][3],
            s_aWorld[3][0], s_aWorld[3][1], s_aWorld[3][2], s_aWorld[3][3]);

    return;
}*/

/**
 * Builds a RGB texture 2D of the given pixels array.
 */
int g3dGenTexture2DRGB(GLubyte *raw_pixs, int w, int h) {
    GLfloat fLargest;
    int texid;

    glGenTextures(1, (unsigned int *) &texid);
    glBindTexture(GL_TEXTURE_2D, texid);

    gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, w, h, GL_RGB, GL_UNSIGNED_BYTE, raw_pixs);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

#ifdef GL_EXT_texture_filter_anisotropic
    glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
#endif

    glBindTexture(GL_TEXTURE_2D, 0);

    return texid;
}

/**
 * Builds a RGBA texture 2D of the given pixels array.
 */
int g3dGenTexture2DRGBA(GLubyte *raw_pixs, int w, int h) {
    GLfloat fLargest;
    int tex_id;

    glGenTextures(1, (unsigned int *) &tex_id);
    glBindTexture(GL_TEXTURE_2D, tex_id);

    gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, w, h, GL_RGBA, GL_UNSIGNED_BYTE, raw_pixs);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

#ifdef GL_EXT_texture_filter_anisotropic
    glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
#endif

    glBindTexture(GL_TEXTURE_2D, 0);

    return tex_id;
}

/**
 * Builds a given type of texture 2D of the given pixels array.
 */
int g3dGenTexture2DType(GLubyte *raw_pixs, GLenum type, int w, int h) {
    GLfloat fLargest;
    int tex_id;

    glGenTextures(1, (unsigned int *) &tex_id);
    glBindTexture(GL_TEXTURE_2D, tex_id);

    gluBuild2DMipmaps(GL_TEXTURE_2D, type, w, h, type, GL_UNSIGNED_BYTE, raw_pixs);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

#ifdef GL_EXT_texture_filter_anisotropic
    glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
#endif

    glBindTexture(GL_TEXTURE_2D, 0);

    return tex_id;
}

/**
 * Builds a given type of texture 2D of the given pixels array.
 * Texture parameter configuration is user defined.
 */
int g3dGenTexture2DTypeConf(GLubyte *raw_pixs, GLenum type, int w, int h, void (*config)(void)) {
    int tex_id;

    glGenTextures(1, (unsigned int *) &tex_id);
    glBindTexture(GL_TEXTURE_2D, tex_id);

    gluBuild2DMipmaps(GL_TEXTURE_2D, type, w, h, type, GL_UNSIGNED_BYTE, raw_pixs);

    if (config) config();

    glBindTexture(GL_TEXTURE_2D, 0);

    return tex_id;
}

#ifdef GL_ARB_texture_float

/**
 * Builds a given type of float texture 2D of the given pixels array.
 */
int g3dGenTexture2DTypeFloat(GLubyte *raw_pixels, GLenum format, GLenum internal, int w, int h) {
    int tex_id;
    GLfloat fLargest;

    glGenTextures(1, (unsigned int *) &tex_id);
    glBindTexture(GL_TEXTURE_2D, tex_id);

    gluBuild2DMipmaps(GL_TEXTURE_2D, internal, w, h, format, GL_FLOAT, raw_pixels);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

#ifdef GL_EXT_texture_filter_anisotropic
    glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
#endif

    glBindTexture(GL_TEXTURE_2D, 0);

    return tex_id;
}

#endif
#ifdef GL_ARB_texture_rectangle

/**
 * Builds a RGB texture rectangle of the given pixels array.
 */
int g3dGenTextureRectRGB(GLubyte *raw_pixels, int w, int h) {
    GLfloat fLargest;
    int tex_id;

    glGenTextures(1, (unsigned int *) &tex_id);
    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, tex_id);

    gluBuild2DMipmaps(GL_TEXTURE_RECTANGLE_ARB, GL_RGB, w, h, GL_RGB, GL_UNSIGNED_BYTE, raw_pixels);

    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

#ifdef GL_EXT_texture_filter_anisotropic
    glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
    glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
#endif

    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);

    return tex_id;
}

/**
 * Builds a RGBA texture rectangle of the given pixels array.
 */
int g3dGenTextureRectRGBA(GLubyte *raw_pixels, int w, int h) {
    GLfloat fLargest;
    int tex_id;

    glGenTextures(1, (unsigned int *) &tex_id);
    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, tex_id);

    gluBuild2DMipmaps(GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, w, h, GL_RGBA, GL_UNSIGNED_BYTE, raw_pixels);

    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

#ifdef GL_EXT_texture_filter_anisotropic
    glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
    glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
#endif

    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);

    return tex_id;
}

/**
 * Builds a given type of texture rectangle of the given pixels array.
 */
int g3dGenTextureRectType(GLubyte *raw_pixels, GLenum type, int w, int h) {
    GLfloat fLargest;
    int tex_id;

    glGenTextures(1, (unsigned int *) &tex_id);
    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, tex_id);

    gluBuild2DMipmaps(GL_TEXTURE_RECTANGLE_ARB, type, w, h, type, GL_UNSIGNED_BYTE, raw_pixels);

    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

#ifdef GL_EXT_texture_filter_anisotropic
    glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
    glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
#endif

    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);

    return tex_id;
}

#endif

/**
 * Builds a RGB texture cube of the given pixels array.
 */
int g3dGenTextureCubeRGB(GLubyte *raw_pixels[6], int w, int h) {
    GLfloat fLargest;
    int tex_id, i;

    glGenTextures(1, (unsigned int *) &tex_id);
    glBindTexture(GL_TEXTURE_CUBE_MAP, tex_id);

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

#ifdef GL_EXT_texture_filter_anisotropic
    glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
    glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
#endif

	for (i = 0; i < 6; ++i) {
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP, GL_TRUE);
		glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, raw_pixels[i]);
	}

    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);

    return tex_id;
}

/**
 * Builds a RGB texture cube of the given pixels array.
 */
int g3dGenTextureCubeBGR(GLubyte *raw_pixels[6], int w, int h) {
    GLfloat fLargest;
    int tex_id, i;

    glGenTextures(1, (unsigned int *) &tex_id);
    glBindTexture(GL_TEXTURE_CUBE_MAP, tex_id);

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

#ifdef GL_EXT_texture_filter_anisotropic
    glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
    glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
#endif

	for (i = 0; i < 6; ++i) {
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP, GL_TRUE);
		glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_BGR, w, h, 0, GL_BGR, GL_UNSIGNED_BYTE, raw_pixels[i]);
	}

    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);

    return tex_id;
}

/**
 * Builds a RGBA texture cube of the given pixels array.
 */
int g3dGenTextureCubeRGBA(GLubyte *raw_pixels[6], int w, int h) {
    GLfloat fLargest;
    int tex_id, i;

    glGenTextures(1, (unsigned int *) &tex_id);
    glBindTexture(GL_TEXTURE_CUBE_MAP, tex_id);

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

#ifdef GL_EXT_texture_filter_anisotropic
    glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
    glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
#endif

	for (i = 0; i < 6; ++i) {
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP, GL_TRUE);
		glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, raw_pixels[i]);
	}

    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);

    return tex_id;
}

/**
 * Builds a given type of texture cube of the given pixels array.
 */
int g3dGenTextureCubeType(GLubyte *raw_pixels[6], GLenum type, int w, int h) {
    GLfloat fLargest;
    int tex_id, i;

    glGenTextures(1, (unsigned int *) &tex_id);
    glBindTexture(GL_TEXTURE_CUBE_MAP, tex_id);

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

#ifdef GL_EXT_texture_filter_anisotropic
    glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
    glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
#endif

	for (i = 0; i < 6; ++i) {
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP, GL_TRUE);
		glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, type, w, h, 0, type, GL_UNSIGNED_BYTE, raw_pixels[i]);
	}

    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);

    return tex_id;
}

/**
 * Builds a given type of float texture cube of the given pixels array.
 */
int g3dGenTextureCubeTypeFloat(GLubyte* raw_pixels[6], GLenum format, GLenum internal, int w, int h) {
    int tex_id, i;
    GLfloat fLargest;

    glGenTextures(1, (unsigned int *) &tex_id);
    glBindTexture(GL_TEXTURE_CUBE_MAP, tex_id);

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

#ifdef GL_EXT_texture_filter_anisotropic
    glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
    glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
#endif

	for (i = 0; i < 6; ++i) {
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP, GL_TRUE);
		glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, internal, w, h, 0, format, GL_FLOAT, raw_pixels[i]);
	}

    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);

    return tex_id;
}

/**
 * Creates an empty texture 2D object.
 */
int g3dGenEmptyTexture2D(int w, int h, int type, int iformat, int format, GLenum target) {
	int tex;

	glGenTextures(1, &tex);
	glBindTexture(target, tex);

	glTexParameterf(target, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameterf(target, GL_TEXTURE_WRAP_T, GL_CLAMP);

	if (format == GL_DEPTH_COMPONENT) {
		glTexParameteri(target, GL_TEXTURE_COMPARE_MODE, GL_NONE);
		glTexParameteri(target, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE);
	}

	glTexParameterf(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glTexParameteri(target, GL_GENERATE_MIPMAP, GL_TRUE);
	glTexImage2D(target, 0, iformat, w, h, 0, format, type, NULL);
	glBindTexture(target, 0);

	return tex;
}

/**
 * Copy image data from one texture to another. (TODO: do it using FBO)
 */
void g3dCopyTexture2D(GLenum type, GLenum format, int w, int h, GLuint src, GLuint dest) {
	if (dest != src && glIsTexture(src) && glIsTexture(dest)) {
		glBindTexture(GL_TEXTURE_2D, src);									// bind source texture
		glBindBuffer(GL_PIXEL_PACK_BUFFER, s_nPBOid);						// bind pixel buffer (PBO) for packing (pixel reading)
		glGetTexImage(GL_TEXTURE_2D, 0, format, type, 0);					// copy source texture image data to the PBO
		glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);								// unbind the PBO
		glBindTexture(GL_TEXTURE_2D, 0);									// unbind source texture

		// Source texture image data should be in the pixel buffer
		
		glBindTexture(GL_TEXTURE_2D, dest);									// bind destination texture
		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, s_nPBOid);						// bind pixel buffer (PBO) for unpacking (pixel drawing)
		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, format, type, 0);		// copy data from bound PBO to the destination texture
		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);							// unbind the PBO
		glBindTexture(GL_TEXTURE_2D, 0);									// unbind destination texture
	}
	return;
}

/**
 * Prepares pixel buffer object for texture image data transfer
 */
void g3dPreparePixelBuffer(int w, int h, int pixel_size) {
	glGenBuffers(1, &s_nPBOid);
	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, s_nPBOid);
	glBufferData(GL_PIXEL_UNPACK_BUFFER, w * h * pixel_size, 0, GL_DYNAMIC_COPY);
	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
	return;
}

/**
 * Loads a texture 2D from file.
 */
int g3dTexture2DType(const char *name, GLuint type) {
    int tex_id = 0;
    SDL_Surface *img = IMG_Load(name);

    if (img) {
        tex_id = g3dGenTexture2DType(img->pixels, type, img->w, img->h);
        SDL_FreeSurface(img);
    } else MGPI_PostMessagef("%s: %s", __FUN__, IMG_GetError());
    return tex_id;
}

/**
 * Loads a float texture 2D from file.
 */
int g3dTexture2DTypeFloat(const char *name, GLuint type, GLuint internal) {
    int tex_id = 0;
    SDL_Surface *img = IMG_Load(name);

    if (img) {
        tex_id = g3dGenTexture2DTypeFloat(img->pixels, type, internal, img->w, img->h);
        SDL_FreeSurface(img);
    } else MGPI_PostMessagef("%s: no such file or directory: \"%s\"", __FUN__, name);
    return tex_id;
}

/* TODO: add a set of g3dGenTexture... */

/**
 * Creates a render buffer of the given size and internal format.
 */
int g3dGenRenderbuffer(GLenum internal_format, int w, int h) {
    int id = 0, size;

	glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &size);

    if (w <= size && h <= size && w > 0 && h > 0) {
        glGenRenderbuffers(1, (GLuint *) &id);
        glBindRenderbuffer(GL_RENDERBUFFER, id);
        glRenderbufferStorage(GL_RENDERBUFFER, internal_format, w, h);
        glBindRenderbuffer(GL_RENDERBUFFER, 0);
    } else MGPI_PostMessagef("%s: the given sizes are too big or sub zero: w = %d, h = %d", __FUN__, w, h);

    return id;
}

/**
 * Creates a multisample render buffer of the given size and internal format.
 */
int g3dGenMultisampleRenderbuffer(GLenum internal_format, int w, int h, int samples) {
    int id = 0, size;

	glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &size);

    if (w <= size && h <= size && w > 0 && h > 0) {
        glGenRenderbuffers(1, (GLuint *) &id);
        glBindRenderbuffer(GL_RENDERBUFFER, id);
		glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, internal_format, w, h);
        glBindRenderbuffer(GL_RENDERBUFFER, 0);
    } else MGPI_PostMessagef("%s: the given sizes are too big or sub zero: w = %d, h = %d", __FUN__, w, h);

    return id;
}
/**
 *
 *
float4 *g3dCalculateTangentBasis(const float3 vTriangle[3], const float2 vTexCoords[3], const float4 N, float4 *vTangent)
{
    float3 dv2v1, dv3v1;
    float dc2c1t, dc2c1b, dc3c1t, dc3c1b;
    float M;
    
	dv2v1[X] = vTriangle[1][X] - vTriangle[0][X];
	dv2v1[Y] = vTriangle[1][Y] - vTriangle[0][Y];
	dv2v1[Z] = vTriangle[1][Z] - vTriangle[0][Z];

	dv3v1[X] = vTriangle[2][X] - vTriangle[0][X];
	dv3v1[Y] = vTriangle[2][Y] - vTriangle[0][Y];
	dv3v1[Z] = vTriangle[2][Z] - vTriangle[0][Z];
    
    dc2c1t = vTexCoords[1][S] - vTexCoords[0][S];
    dc2c1b = vTexCoords[1][T] - vTexCoords[0][T];
    dc3c1t = vTexCoords[2][S] - vTexCoords[0][S];
    dc3c1b = vTexCoords[2][T] - vTexCoords[0][T];
    
    M = (dc2c1t * dc3c1b) - (dc3c1t * dc2c1b);
    M = 1.0f / M;
    
	dv2v1[X] *= dc3c1b;
	dv2v1[Y] *= dc3c1b;
	dv2v1[Z] *= dc3c1b;

	dv3v1[X] *= dc2c1b;
	dv3v1[Y] *= dc2c1b;
	dv3v1[Z] *= dc2c1b;
    
	(*vTangent)[X] = (dv2v1[X] - dv3v1[X]) * M;
	(*vTangent)[Y] = (dv2v1[Y] - dv3v1[Y]) * M;
	(*vTangent)[Z] = (dv2v1[Z] - dv3v1[Z]) * M;
	MGPI_Normalize(vTangent);

    float4 B;
	MGPI_CrossProduct(&B, N, (*vTangent));
    MGPI_CrossProduct(vTangent, B, N);
    MGPI_Normalize(vTangent);

	return vTangent;
}*/

/**
 * Creates and returns a rotation matrix of the given parameters, in a form of a float pointer.
 */
float *g3dGenRotationMatrixf(float angle, float x, float y, float z) {/* TEST IT!!! */
    float *matrix = matrix4x4();
    quat_t quat = MGPI_DEFAULT_VECTOR;
    float xx, yy, zz, xy, xz, yz, wx, wy, wz;

    MGPI_CollectGarbage(matrix);

    quat[X] = x * sin((-angle / 2));
    quat[Y] = y * sin((-angle / 2));
    quat[Z] = z * sin((-angle / 2));
    quat[W] = cos((-angle / 2));

    xx = quat[X] * quat[X];
    yy = quat[Y] * quat[Y];
    zz = quat[Z] * quat[Z];

    xy = quat[X] * quat[Y];
    xz = quat[X] * quat[Z];

    yz = quat[Y] * quat[Z];

    wx = quat[W] * quat[X];
    wy = quat[W] * quat[Y];
    wz = quat[W] * quat[Z];

    matrix[M11] = 1.0f - 2.0f * (yy + zz);
    matrix[M12] = 2.0f * (xy + wz);
    matrix[M13] = 2.0f * (xz - wy);
    matrix[M14] = 0.0f;
    matrix[M21] = 2.0f * (xy - wz);
    matrix[M22] = 1.0f - 2.0f * (xx + zz);
    matrix[M23] = 2.0f * (yz + wx);
    matrix[M24] = 0.0f;
    matrix[M31] = 2.0f * (xz + wy);
    matrix[M32] = 2.0f * (yz - wx);
    matrix[M33] = 1.0f - 2.0f * (xx + yy);
    matrix[M34] = 0.0f;
    matrix[M41] = 0.0f;
    matrix[M42] = 0.0f;
    matrix[M43] = 0.0f;
    matrix[M44] = 1.0f;

    return matrix;
}

/**
 * Creates and returns a rotation matrix of the given parameters, in a form of a float pointer.
 */
float *g3dGenRotationMatrixv(float angle, float4 axis) {/* TEST IT!!! */
    float *matrix = matrix4x4();
    quat_t quat = MGPI_DEFAULT_VECTOR;
    float xx, yy, zz, xy, xz, yz, wx, wy, wz;

    MGPI_CollectGarbage(matrix);

    quat[X] = axis[X] * sin((-angle / 2));
    quat[Y] = axis[Y] * sin((-angle / 2));
    quat[Z] = axis[Z] * sin((-angle / 2));
    quat[W] = cos((-angle / 2));

    xx = quat[X] * quat[X];
    yy = quat[Y] * quat[Y];
    zz = quat[Z] * quat[Z];

    xy = quat[X] * quat[Y];
    xz = quat[X] * quat[Z];

    yz = quat[Y] * quat[Z];

    wx = quat[W] * quat[X];
    wy = quat[W] * quat[Y];
    wz = quat[W] * quat[Z];

    matrix[M11] = 1.0f - 2.0f * (yy + zz);
    matrix[M12] = 2.0f * (xy + wz);
    matrix[M13] = 2.0f * (xz - wy);
    matrix[M14] = 0.0f;
    matrix[M21] = 2.0f * (xy - wz);
    matrix[M22] = 1.0f - 2.0f * (xx + zz);
    matrix[M23] = 2.0f * (yz + wx);
    matrix[M24] = 0.0f;
    matrix[M31] = 2.0f * (xz + wy);
    matrix[M32] = 2.0f * (yz - wx);
    matrix[M33] = 1.0f - 2.0f * (xx + yy);
    matrix[M34] = 0.0f;
    matrix[M41] = 0.0f;
    matrix[M42] = 0.0f;
    matrix[M43] = 0.0f;
    matrix[M44] = 1.0f;

    return matrix;
}

/**
 * Creates and returns a rotation matrix for the given orientation quaternion.
 */
float *g3dGenRotationMatrixq(quat_t orientation) {/* TEST IT!!! */
    float *matrix = matrix4x4();
    float xx, yy, zz, xy, xz, yz, wx, wy, wz;

    MGPI_CollectGarbage(matrix);

    xx = orientation[X] * orientation[X];
    yy = orientation[Y] * orientation[Y];
    zz = orientation[Z] * orientation[Z];

    xy = orientation[X] * orientation[Y];
    xz = orientation[X] * orientation[Z];

    yz = orientation[Y] * orientation[Z];

    wx = orientation[W] * orientation[X];
    wy = orientation[W] * orientation[Y];
    wz = orientation[W] * orientation[Z];

    matrix[M11] = 1.0f - 2.0f * (yy + zz);
    matrix[M12] = 2.0f * (xy + wz);
    matrix[M13] = 2.0f * (xz - wy);
    matrix[M14] = 0.0f;
    matrix[M21] = 2.0f * (xy - wz);
    matrix[M22] = 1.0f - 2.0f * (xx + zz);
    matrix[M23] = 2.0f * (yz + wx);
    matrix[M24] = 0.0f;
    matrix[M31] = 2.0f * (xz + wy);
    matrix[M32] = 2.0f * (yz - wx);
    matrix[M33] = 1.0f - 2.0f * (xx + yy);
    matrix[M34] = 0.0f;
    matrix[M41] = 0.0f;
    matrix[M42] = 0.0f;
    matrix[M43] = 0.0f;
    matrix[M44] = 1.0f;

    return matrix;
}

/**
 * Creates and returns a translation matrix of the given parameters, in a form of a float pointer.
 */
float *g3dGenTranslationMatrixf(float x, float y, float z) {/* TEST IT!!! */
    float *matrix = matrix4x4();
    MGPI_CollectGarbage(matrix);

    matrix[M11] = 1.0f;
    matrix[M12] = 0.0f;
    matrix[M13] = 0.0f;
    matrix[M14] = x;
    matrix[M21] = 0.0f;
    matrix[M22] = 1.0f;
    matrix[M23] = 0.0f;
    matrix[M24] = y;
    matrix[M31] = 0.0f;
    matrix[M32] = 0.0f;
    matrix[M33] = 1.0f;
    matrix[M34] = z;
    matrix[M41] = 0.0f;
    matrix[M42] = 0.0f;
    matrix[M43] = 0.0f;
    matrix[M44] = 1.0f;

    return matrix;
}

/**
 * Creates and returns a translation matrix of the given parameters, in a form of a float pointer.
 */
float *g3dGenTranslationMatrixv(float4 vect) {/* TEST IT!!! */
    float *matrix = matrix4x4();
    MGPI_CollectGarbage(matrix);

    matrix[M11] = 1.0f;
    matrix[M12] = 0.0f;
    matrix[M13] = 0.0f;
    matrix[M14] = vect[X];
    matrix[M21] = 0.0f;
    matrix[M22] = 1.0f;
    matrix[M23] = 0.0f;
    matrix[M24] = vect[Y];
    matrix[M31] = 0.0f;
    matrix[M32] = 0.0f;
    matrix[M33] = 1.0f;
    matrix[M34] = vect[Z];
    matrix[M41] = 0.0f;
    matrix[M42] = 0.0f;
    matrix[M43] = 0.0f;
    matrix[M44] = vect[W];

    return matrix;
}

/**
 * Creates and returns a scaling matrix of the given parameters, in a form of a float pointer.
 */
float *g3dGenScalingMatrixf(float x, float y, float z) {/* TEST IT!!! */
    float *matrix = matrix4x4();
    MGPI_CollectGarbage(matrix);

    matrix[M11] = x;
    matrix[M12] = 0.0f;
    matrix[M13] = 0.0f;
    matrix[M14] = 0.0f;
    matrix[M21] = 0.0f;
    matrix[M22] = y;
    matrix[M23] = 0.0f;
    matrix[M24] = 0.0f;
    matrix[M31] = 0.0f;
    matrix[M32] = 0.0f;
    matrix[M33] = z;
    matrix[M34] = 0.0f;
    matrix[M41] = 0.0f;
    matrix[M42] = 0.0f;
    matrix[M43] = 0.0f;
    matrix[M44] = 1.0f;

    return matrix;
}

/**
 * Creates and returns a scaling matrix of the given parameters, in a form of a float pointer.
 */
float *g3dGenScalingMatrixv(float4 vect) {/* TEST IT!!! */
    float *matrix = matrix4x4();
    MGPI_CollectGarbage(matrix);

    matrix[M11] = vect[X];
    matrix[M12] = 0.0f;
    matrix[M13] = 0.0f;
    matrix[M14] = 0.0f;
    matrix[M21] = 0.0f;
    matrix[M22] = vect[Y];
    matrix[M23] = 0.0f;
    matrix[M24] = 0.0f;
    matrix[M31] = 0.0f;
    matrix[M32] = 0.0f;
    matrix[M33] = vect[Z];
    matrix[M34] = 0.0f;
    matrix[M41] = 0.0f;
    matrix[M42] = 0.0f;
    matrix[M43] = 0.0f;
    matrix[M44] = vect[W];

    return matrix;
}

/**
 * Creates and return a shear matrix containing skews of X, Y and Z axis.
 */
float MGPI_API *g3dGenShearMatrixf(float skewX, float skewY, float skewOnX, float skewOnY) {
    float *matrix = matrix4x4();
    MGPI_CollectGarbage(matrix);

    matrix[M11] = 1.0f;
    matrix[M12] = skewX;
    matrix[M13] = 0.0f;
    matrix[M14] = 0.0f;
    matrix[M21] = skewY;
    matrix[M22] = 1.0f;
    matrix[M23] = 0.0f;
    matrix[M24] = 0.0f;
    matrix[M31] = 0.0f;
    matrix[M32] = skewOnY;
    matrix[M33] = 1.0f;
    matrix[M34] = 0.0f;
    matrix[M41] = 0.0f;
    matrix[M42] = skewOnX;
    matrix[M43] = 0.0f;
    matrix[M44] = 1.0f;

    return matrix;
}

/**
 * Sets a given camera view port with the given sizes.
 */
int g3dSetCamViewport(int cam, int w, int h) {
    int index = cam - g_nHandleBase;
    cam_t *tmp = (g_aSceneCameras + index);

    tmp->w = w;
    tmp->h = h;

    return (!0);
}

/**
 * Allocates a camera object in the engine memory. The position is set to the given.
 */
int g3dAllocVectCam(float4 point) {
    int check = 0;
    cam_t *temp = g_aSceneCameras;

    while (temp->used) ++check;
    if (check >= MGPI_MAX_SCENE_CAMERAS) return -1;
    else {
        temp->used = 1;

        temp->position[0] = point[X];
        temp->position[1] = point[Y];
        temp->position[2] = point[Z];
        temp->position[3] = 1.0f;

        temp->r[0] = 1.0f;
        temp->r[1] = 0.0f;
        temp->r[2] = 0.0f;
        temp->r[3] = 1.0f;

        temp->u[0] = 0.0f;
        temp->u[1] = 1.0f;
        temp->u[2] = 0.0f;
        temp->u[3] = 1.0f;

        temp->f[0] = 0.0f;
        temp->f[1] = 0.0f;
        temp->f[2] = -1.0f;
        temp->f[3] = 1.0f;

        temp->fovy = 60.0f;
        temp->far_clip_dist = 100000.0f;
        temp->near_clip_dist = 0.1f;

        temp->mode = MGPI_CAMERA_QUATERNIONS;
        temp->max_ypr[X] = 2.0f * MGPI_PI;
        temp->min_ypr[X] = 0.0f;

        temp->max_ypr[Y] = MGPI_PI;
        temp->min_ypr[Y] = -MGPI_PI;

        temp->max_ypr[Z] = 2.0f * MGPI_PI;
        temp->min_ypr[Z] = 0.0f;

		s_fDepthScale = (1.0f / (temp->far_clip_dist - temp->near_clip_dist));// produce a getter...
		glPixelTransferf(GL_DEPTH_SCALE, s_fDepthScale);//??????????????????
    }
    return (check + g_nHandleBase);
}

/**
 * Gets a defined by the parameter given camera vector.
 */
int g3dGetCamVect(float4 *pv, int cam, int param) {
    int index = cam - g_nHandleBase;
    cam_t *tmp = (g_aSceneCameras + index);

    switch (param) {
        case MGPI_CAMERA_RIGHT:
            (*pv)[0] = tmp->r[0];
            (*pv)[1] = tmp->r[1];
            (*pv)[2] = tmp->r[2];
            (*pv)[3] = tmp->r[3];
            break;

        case MGPI_CAMERA_UP:
            (*pv)[0] = tmp->u[0];
            (*pv)[1] = tmp->u[1];
            (*pv)[2] = tmp->u[2];
            (*pv)[3] = tmp->u[3];
            break;

        case MGPI_CAMERA_FORWARD:
            (*pv)[0] = tmp->f[0];
            (*pv)[1] = tmp->f[1];
            (*pv)[2] = tmp->f[2];
            (*pv)[3] = tmp->f[3];
            break;

        case MGPI_CAMERA_POSITION:
            (*pv)[0] = tmp->position[0];
            (*pv)[1] = tmp->position[1];
            (*pv)[2] = tmp->position[2];
            (*pv)[3] = tmp->position[3];
            break;

        case MGPI_CAMERA_YAW_PITCH_ROLL:
            (*pv)[0] = tmp->yaw_pitch_roll[0];
            (*pv)[1] = tmp->yaw_pitch_roll[1];
            (*pv)[2] = tmp->yaw_pitch_roll[2];
            (*pv)[3] = 1.0f;
            break;

        default: return 0;
    }

    return (!0);
}

/**
 * Sets camera vectors.
 */
int g3dSetCamVect(float4 vector, int cam, int param) {
    int index = cam - g_nHandleBase;
    cam_t *tmp = (g_aSceneCameras + index);

    switch (param) {
        case MGPI_CAMERA_RIGHT:
            tmp->r[0] = vector[0];
            tmp->r[1] = vector[1];
            tmp->r[2] = vector[2];
            tmp->r[3] = vector[3];
            break;

        case MGPI_CAMERA_UP:
            tmp->u[0] = vector[0];
            tmp->u[1] = vector[1];
            tmp->u[2] = vector[2];
            tmp->u[3] = vector[3];
            break;

        case MGPI_CAMERA_FORWARD:
            tmp->f[0] = vector[0];
            tmp->f[1] = vector[1];
            tmp->f[2] = vector[2];
            tmp->f[3] = vector[3];
            break;

        case MGPI_CAMERA_POSITION:
            tmp->position[0] = vector[0];
            tmp->position[1] = vector[1];
            tmp->position[2] = vector[2];
            tmp->position[3] = vector[3];
            break;

        case MGPI_CAMERA_MAX_YAW_PITCH_ROLL:
            tmp->max_ypr[0] = vector[0];
            tmp->max_ypr[1] = vector[1];
            tmp->max_ypr[2] = vector[2];
            break;

        case MGPI_CAMERA_MIN_YAW_PITCH_ROLL:
            tmp->min_ypr[0] = vector[0];
            tmp->min_ypr[1] = vector[1];
            tmp->min_ypr[2] = vector[2];
            break;

        case MGPI_CAMERA_YAW_PITCH_ROLL:
            tmp->yaw_pitch_roll[0] = vector[0];
            tmp->yaw_pitch_roll[1] = vector[1];
            tmp->yaw_pitch_roll[2] = vector[2];
            break;

        default: return 0;
    }

    return (!0);
}

/**
 * Sets the given camera as the current camera.
 */
int g3dSetCurrentCam(int camera) {
    g_nCurrentCameraHandle = camera;
    return camera;
}

/**
 * Sets camera rotation mode.
 */
int g3dSetCamMode(int cam, int mode) {
    int index = cam - g_nHandleBase;
    cam_t *tmp = (g_aSceneCameras + index);
    tmp->mode = mode;

    return (!0);
}

/**
 * Frees the given camera form the engive memory.
 */
int g3dFreeVectorCam(int cam) {
    int index = cam - g_nHandleBase;
    cam_t *tmp = (g_aSceneCameras + index);
    free(tmp);
    return (!0);
}

/**
 * Sets the given vector as an input transformation vector.
 */
int g3dInputv(float4 pos) {
    s_aVector[X] = pos[0];
    s_aVector[Y] = pos[1];
    s_aVector[Z] = pos[2];
    s_aVector[W] = pos[3];

    return (!0);
}

/**
 * Sets the given values as an input transformation vector.
 */
int g3dInputf(float x, float y, float z) {
    s_aVector[X] = x;
    s_aVector[Y] = y;
    s_aVector[Z] = z;
    s_aVector[W] = 1.0f;

    return (!0);
}

/**
 * Sets the given values as an input transformation vector.
 */
int g3dInputi(int x, int y, int z) {
    s_aVector[X] = (float) x;
    s_aVector[Y] = (float) y;
    s_aVector[Z] = (float) z;
    s_aVector[W] = 1.0f;

    return (!0);
}

/**
 * Causes the input transformation vector to be translated by the given vector.
 */
int g3dTranslatev(float4 move) {
    float4 t = MGPI_DEFAULT_VECTOR;
    float4x4 trans = MGPI_IDENTITY, m = MGPI_IDENTITY;

    m[0][3] = move[X];
    m[1][3] = move[Y];
    m[2][3] = move[Z];

    if (s_nMatrix == MGPI_WORLD) {
        MGPI_MatrixMul4(&trans, s_aWorld, m);
        g3dLoadMatrix(trans);
    } else {
        MGPI_MatrixMulVect4(&t, m, s_aVector);
    }

    s_aVector[X] = (t[X] / t[W]);
    s_aVector[Y] = (t[Y] / t[W]);
    s_aVector[Z] = (t[Z] / t[W]);
    s_aVector[W] = (t[W] / t[W]);
    return (!0);
}

/**
 * Causes the input transformation vector to be translated by the given values.
 */
int g3dTranslatef(float dx, float dy, float dz) {
    float4 move = MGPI_DEFAULT_VECTOR, t = MGPI_DEFAULT_VECTOR;
    float4x4 trans = MGPI_IDENTITY, m = MGPI_IDENTITY;

    move[0] = dx;
    move[1] = dy;
    move[2] = dz;

    m[0][3] = move[X];
    m[1][3] = move[Y];
    m[2][3] = move[Z];

    if (s_nMatrix == MGPI_WORLD) {
        MGPI_MatrixMul4(&trans, s_aWorld, m);
        g3dLoadMatrix(trans);
    } else {
        MGPI_MatrixMulVect4(&t, m, s_aVector);
    }

    s_aVector[X] = (t[X] / t[W]);
    s_aVector[Y] = (t[Y] / t[W]);
    s_aVector[Z] = (t[Z] / t[W]);
    s_aVector[W] = (t[W] / t[W]);
    return (!0);
}

/**
 * Causes the input transformation vector to be translated by the given values.
 */
int g3dTranslatei(int dx, int dy, int dz) {
    float4 move = MGPI_DEFAULT_VECTOR, t = MGPI_DEFAULT_VECTOR;
    float4x4 trans = MGPI_IDENTITY, m = MGPI_IDENTITY;

    move[0] = (float) dx;
    move[1] = (float) dy;
    move[2] = (float) dz;

    m[0][3] = move[X];
    m[1][3] = move[Y];
    m[2][3] = move[Z];

    if (s_nMatrix == MGPI_WORLD) {
        MGPI_MatrixMul4(&trans, s_aWorld, m);
        g3dLoadMatrix(trans);
    } else {
        MGPI_MatrixMulVect4(&t, m, s_aVector);
    }

    s_aVector[X] = (t[X] / t[W]);
    s_aVector[Y] = (t[Y] / t[W]);
    s_aVector[Z] = (t[Z] / t[W]);
    s_aVector[W] = (t[W] / t[W]);
    return (!0);
}

/**
 * Rotates a point over an axis by the angle given.
 */
int g3dRotatef(float angel, float4 axis) {
    float4x4 Mr = MGPI_IDENTITY, trans = MGPI_IDENTITY;
    quat_t quat = MGPI_DEFAULT_VECTOR;
    float4 t = MGPI_DEFAULT_VECTOR;

    float xx, yy, zz, xy, xz, yz, wx, wy, wz;

    quat[X] = axis[X] * sin((-angel / 2));
    quat[Y] = axis[Y] * sin((-angel / 2));
    quat[Z] = axis[Z] * sin((-angel / 2));
    quat[W] = cos((-angel / 2));

    xx = quat[X] * quat[X];
    yy = quat[Y] * quat[Y];
    zz = quat[Z] * quat[Z];

    xy = quat[X] * quat[Y];
    xz = quat[X] * quat[Z];

    yz = quat[Y] * quat[Z];

    wx = quat[W] * quat[X];
    wy = quat[W] * quat[Y];
    wz = quat[W] * quat[Z];

    Mr[0][0] = 1.0f - 2.0f * (yy + zz);
    Mr[0][1] = 2.0f * (xy + wz);
    Mr[0][2] = 2.0f * (xz - wy);
    Mr[0][3] = 0.0f;
    Mr[1][0] = 2.0f * (xy - wz);
    Mr[1][1] = 1.0f - 2.0f * (xx + zz);
    Mr[1][2] = 2.0f * (yz + wx);
    Mr[1][3] = 0.0f;
    Mr[2][0] = 2.0f * (xz + wy);
    Mr[2][1] = 2.0f * (yz - wx);
    Mr[2][2] = 1.0f - 2.0f * (xx + yy);
    Mr[2][3] = 0.0f;
    Mr[3][0] = 0.0f;
    Mr[3][1] = 0.0f;
    Mr[3][2] = 0.0f;
    Mr[3][3] = 1.0f;

    if (s_nMatrix == MGPI_WORLD) {
        MGPI_MatrixMul4(&trans, s_aWorld, Mr);
        g3dLoadMatrix(trans);
    } else {
        MGPI_MatrixMulVect4(&t, Mr, s_aVector);
    }

    s_aVector[0] = t[0];
    s_aVector[1] = t[1];
    s_aVector[2] = t[2];
    s_aVector[3] = t[3];

    return (!0);
}

/**
 * Rotates a point over an axis by the angle given.
 */
int g3dRotatei(int angle, float4 axis) {
    return g3dRotatef((float) angle, axis);
}

/**
 * Rotates a point using the given rotation matrix.
 */
int g3dRotatem(float4x4 m) {
    float4 t = MGPI_DEFAULT_VECTOR;
    float4x4 trans = MGPI_IDENTITY;

    if (s_nMatrix == MGPI_WORLD) {
        MGPI_MatrixMul4(&trans, s_aWorld, m);
        g3dLoadMatrix(trans);
    } else {
        MGPI_MatrixMulVect4(&t, m, s_aVector);
    }

    s_aVector[0] = t[0];
    s_aVector[1] = t[1];
    s_aVector[2] = t[2];
    s_aVector[3] = t[3];

    return (!0);
}

/**
 * Scales a point using the given scale vector.
 */
int g3dScalev(float4 scale) {
    float4 t = MGPI_DEFAULT_VECTOR;
    float4x4 trans = MGPI_IDENTITY, m = MGPI_IDENTITY;

    m[0][0] = scale[X];
    m[1][1] = scale[Y];
    m[2][2] = scale[Z];

    if (s_nMatrix == MGPI_WORLD) {
        MGPI_MatrixMul4(&trans, s_aWorld, m);
        g3dLoadMatrix(trans);
    } else {
        MGPI_MatrixMulVect4(&t, m, s_aVector);
    }

    s_aVector[X] = (t[X] / t[W]);
    s_aVector[Y] = (t[Y] / t[W]);
    s_aVector[Z] = (t[Z] / t[W]);
    s_aVector[W] = (t[W] / t[W]);
    return (!0);
}

/**
 * Scales a point using the given scale float values.
 */
int g3dScalef(float x, float y, float z) {
    float4 t = MGPI_DEFAULT_VECTOR;
    float4 scale = MGPI_DEFAULT_VECTOR;
    float4x4 trans = MGPI_IDENTITY, m = MGPI_IDENTITY;

    scale[X] = x;
    scale[Y] = y;
    scale[Z] = z;

    m[0][0] = scale[X];
    m[1][1] = scale[Y];
    m[2][2] = scale[Z];

    MGPI_MatrixMul4(&trans, s_aWorld, m);
    g3dLoadMatrix(trans);

    s_aVector[X] = (t[X] / t[W]);
    s_aVector[Y] = (t[Y] / t[W]);
    s_aVector[Z] = (t[Z] / t[W]);
    s_aVector[W] = (t[W] / t[W]);
    return (!0);
}

/**
 * Scales a point using the given scale int values.
 */
int g3dScalei(int x, int y, int z) {
    float4 t = MGPI_DEFAULT_VECTOR;
    float4 scale = MGPI_DEFAULT_VECTOR;
    float4x4 trans = MGPI_IDENTITY, m = MGPI_IDENTITY;

    scale[X] = (float) x;
    scale[Y] = (float) y;
    scale[Z] = (float) z;

    m[0][0] = scale[X];
    m[1][1] = scale[Y];
    m[2][2] = scale[Z];

    MGPI_MatrixMul4(&trans, s_aWorld, m);
    g3dLoadMatrix(trans);

    s_aVector[X] = (t[X] / t[W]);
    s_aVector[Y] = (t[Y] / t[W]);
    s_aVector[Z] = (t[Z] / t[W]);
    s_aVector[W] = (t[W] / t[W]);
    return (!0);
}

/**
 * Gets an output transformation vector and causes the input to be cleared.
 */
int g3dOutputv(float4 *point) {
    (*point)[X] = s_aVector[X];
    (*point)[Y] = s_aVector[Y];
    (*point)[Z] = s_aVector[Z];
    (*point)[W] = s_aVector[W];

    s_aVector[X] = 0.0f;
    s_aVector[Y] = 0.0f;
    s_aVector[Z] = 0.0f;
    s_aVector[W] = 1.0f;
    return (!0);
}

/**
 * Loads a given matrix as transformation matrix.
 */
int g3dLoadMatrix(float4x4 m) {
    int i, j;
    for (i = 0; i < 4; ++i)
        for (j = 0; j < 4; ++j)
            s_aWorld[i][j] = m[i][j];

    return (!0);
}

/**
 * Loads an identity matrix to the trasformation matrix.
 */
int g3dLoadIdentity(void) {
    s_aWorld[0][0] = 1.0f;
    s_aWorld[0][1] = 0;
    s_aWorld[0][2] = 0;
    s_aWorld[0][3] = 0;
    s_aWorld[1][0] = 0;
    s_aWorld[1][1] = 1.0f;
    s_aWorld[1][2] = 0;
    s_aWorld[1][3] = 0;
    s_aWorld[2][0] = 0;
    s_aWorld[2][1] = 0;
    s_aWorld[2][2] = 1.0f;
    s_aWorld[2][3] = 0;
    s_aWorld[3][0] = 0;
    s_aWorld[3][1] = 0;
    s_aWorld[3][2] = 0;
    s_aWorld[3][3] = 1.0f;

    return (!0);
}

/**
 * Multiplies given input vector with the current transformation matrix.
 */
int g3dTransform(float4 *product) {
    MGPI_MatrixMulVect4(product, s_aWorld, s_aVector);
    MGPI_Uniform4(product);

    return (!0);
}

/**
 * Rotates the given camera over the given axis by the given angle.
 */
int g3dRotateCamf(int cam, float angel, float4 axis) {
    int index = cam - g_nHandleBase;
    cam_t *tmp = (g_aSceneCameras + index);

    s_nMatrix = MGPI_VIEW;

    g3dInputv(tmp->f);
    g3dRotatef(angel, axis);
    g3dOutputv(&tmp->f);
    MGPI_Uniform4(&tmp->f);

    g3dInputv(tmp->u);
    g3dRotatef(angel, axis);
    g3dOutputv(&tmp->u);
    MGPI_Uniform4(&tmp->u);

    g3dInputv(tmp->r);
    g3dRotatef(angel, axis);
    g3dOutputv(&tmp->r);
    MGPI_Uniform4(&tmp->r);

    s_nMatrix = MGPI_WORLD;

    return (!0);
}

/**
 * Rotates the given camera over the given axis by the given angle.
 */
int g3dRotateCami(int cam, int angel, float4 axis) {
    return g3dRotateCamf(cam, (float) angel, axis);
}

/**
 * Rotates the given camera by the yaw, pitch and roll angles.
 */
int g3dRotateCamypr(int cam, float yaw, float pitch, float roll) {
    int index = cam - g_nHandleBase;
    cam_t *tmp = (g_aSceneCameras + index);

    if (tmp->yaw_pitch_roll[X] + yaw < tmp->max_ypr[X] && tmp->yaw_pitch_roll[X] + yaw > tmp->min_ypr[X])
        tmp->yaw_pitch_roll[X] += yaw;

    if (tmp->yaw_pitch_roll[Y] + pitch < tmp->max_ypr[Y] && tmp->yaw_pitch_roll[Y] + pitch > tmp->min_ypr[Y])
        tmp->yaw_pitch_roll[Y] += pitch;

    if (tmp->yaw_pitch_roll[Z] + roll < tmp->max_ypr[Z] && tmp->yaw_pitch_roll[Z] + roll > tmp->min_ypr[Z])
        tmp->yaw_pitch_roll[Z] += roll;

    return (!0);
}

/**
 * Translates the given camera by the given vector.
 */
int g3dTranslateCamv(int cam, float4 move) {
    int index = cam - g_nHandleBase;
    cam_t *tmp = (g_aSceneCameras + index);

    s_nMatrix = MGPI_VIEW;

    g3dInputv(tmp->position);
    g3dTranslatev(move);
    g3dOutputv(&tmp->position);

    s_nMatrix = MGPI_WORLD;

    return (!0);
}

/**
 * Translates the given camera by the given values.
 */
int g3dTranslateCamf(int cam, float dx, float dy, float dz) {
    int index = cam - g_nHandleBase;
    cam_t *tmp = (g_aSceneCameras + index);

    s_nMatrix = MGPI_VIEW;

    g3dInputv(tmp->position);
    g3dTranslatef(dx, dy, dz);
    g3dOutputv(&tmp->position);

    s_nMatrix = MGPI_WORLD;

    return (!0);
}

/**
 * Translates the given camera by the given values.
 */
int g3dTranslateCami(int cam, int dx, int dy, int dz) {
    int index = cam - g_nHandleBase;
    cam_t *tmp = (g_aSceneCameras + index);

    s_nMatrix = MGPI_VIEW;

    g3dInputv(tmp->position);
    g3dTranslatei(dx, dy, dz);
    g3dOutputv(&tmp->position);

    s_nMatrix = MGPI_WORLD;

    return (!0);
}

/**
 * Changes the value of the given camera field-of-view angle by the given zoom.
 */
int g3dZoomCamf(int cam, float zoom) {
    int index = cam - g_nHandleBase;
    cam_t *tmp = (g_aSceneCameras + index);
    tmp->fovy += zoom;
    return (!0);
}

/**
 * Changes the value of the given camera field-of-view angle by the given zoom.
 */
int g3dZoomCami(int cam, int zoom) {
    int index = cam - g_nHandleBase;
    cam_t *tmp = (g_aSceneCameras + index);
    tmp->fovy += (float) zoom;
    return (!0);
}

/**
 * Rebuilds a projection matrix for an orthographic camera to defalut values.
 */
int g3dOrthReshape(int w, int h) {
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    if (g_fProjection) glMultMatrixf(g_fProjection);

    glOrtho(0, w, h, 0, 0, 1);
    return (!0);
}

/**
 * Rebuilds a projection matrix for an perspective camera to defalut values.
 */
int g3dPersReshape(int w, int h) {
    if (!h) h = 1;

    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    if (g_fProjection) glMultMatrixf(g_fProjection);

    gluPerspective(60.0f, (float) w / (float) h, 1.0f, 10000.0f);
    return (!0);
}

/**
 * Rebuilds a projection matrix for the given perspective camera.
 */
int g3dCameraPersReshape(int cam) {
    int index = cam - g_nHandleBase;
    cam_t *tmp = (g_aSceneCameras + index);

    if (!tmp->h) tmp->h = 1;

    glViewport(0, 0, tmp->w, tmp->h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    if (g_fProjection) glMultMatrixf(g_fProjection);

    gluPerspective(tmp->fovy, (float) tmp->w / (float) tmp->h, tmp->near_clip_dist, tmp->far_clip_dist);
    return (!0);
}

/**
 * Rebuilds a projection matrix for the given orthographic camera.
 */
int g3dCameraOrthReshape(int cam) {
    int index = cam - g_nHandleBase;
    cam_t *tmp = (g_aSceneCameras + index);

    glViewport(0, 0, tmp->w, tmp->h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    if (g_fProjection) glMultMatrixf(g_fProjection);

    glOrtho(0, tmp->w, tmp->h, 0, -tmp->near_clip_dist, tmp->far_clip_dist);
    return (!0);
}

/**
 * Rebuilds a model view matrix for the given camera.
 */
int g3dUpdateCam(int cam) {
    int index = cam - g_nHandleBase;
    cam_t *tmp = (g_aSceneCameras + index);

    if (tmp->is_perspective) {
        g3dCameraPersReshape(cam);
    } else {
        g3dCameraOrthReshape(cam);
    }

    if (tmp->mode == MGPI_CAMERA_YAW_PITCH_ROLL) {
        /* TODO: aquire the look-at point and up vector according to the current yaw-pitch-roll angles */
    } else {

        MGPI_Uniform4(&tmp->f);/* ????: normalization issue... */
        MGPI_Uniform4(&tmp->u);

        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        if (g_fModelView) glMultMatrixf(g_fModelView);

        gluLookAt(tmp->position[0], tmp->position[1], tmp->position[2],
                tmp->position[0] + tmp->f[0],
                tmp->position[1] + tmp->f[1],
                tmp->position[2] + tmp->f[2],
                tmp->u[0], tmp->u[1], tmp->u[2]);
    }

    return (!0);
}

/**
 * Draws coordinates system vectors.
 */
void g3dDrawCoordinates(void) {
    MGPI_BOOL was_blend = MGPI_FALSE;
    MGPI_BOOL was_texture = MGPI_FALSE;
    MGPI_BOOL was_lighting = MGPI_FALSE;
    MGPI_BOOL was_fog = MGPI_FALSE;
	GLUquadricObj *pObj;	// Temporary, used for quadrics
	
	// Measurements
	float fAxisRadius = 0.025f;
	float fAxisHeight = 1.0f;
	float fArrowRadius = 0.06f;
	float fArrowHeight = 0.1f;

    if (glIsEnabled(GL_BLEND)) {
        glDisable(GL_BLEND);
        was_blend = MGPI_TRUE;
    }

    if (glIsEnabled(GL_TEXTURE_2D)) {
        glDisable(GL_TEXTURE_2D);
        was_texture = MGPI_TRUE;
    }

    if (glIsEnabled(GL_LIGHTING)) {
        glDisable(GL_LIGHTING);
        was_lighting = MGPI_TRUE;
    }

    if (glIsEnabled(GL_FOG)) {
        glDisable(GL_FOG);
        was_fog = MGPI_TRUE;
    }

    glUseProgram(0);

    glPushMatrix();
    {
        //glLineWidth(3.0f);
        //glBegin(GL_LINES);
        //{
        //    glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
        //    glVertex3f(0.0f, 0.0f, 0.0f);
        //    glVertex3f(1.0f, 0.0f, 0.0f);

        //    glVertex3f(1.0f, 0.0f, 0.0f);
        //    glVertex3f(0.8f, 0.2f, 0.0f);
        //    glVertex3f(1.0f, 0.0f, 0.0f);
        //    glVertex3f(0.8f, -0.2f, 0.0f);

        //    glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
        //    glVertex3f(0.0f, 0.0f, 0.0f);
        //    glVertex3f(0.0f, 1.0f, 0.0f);

        //    glVertex3f(0.0f, 1.0f, 0.0f);
        //    glVertex3f(-0.2f, 0.8f, 0.0f);
        //    glVertex3f(0.0f, 1.0f, 0.0f);
        //    glVertex3f(0.2f, 0.8f, 0.0f);

        //    glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
        //    glVertex3f(0.0f, 0.0f, 0.0f);
        //    glVertex3f(0.0f, 0.0f, 1.0f);

        //    glVertex3f(0.0f, 0.0f, 1.0f);
        //    glVertex3f(0.2f, 0.0f, 0.8f);
        //    glVertex3f(0.0f, 0.0f, 1.0f);
        //    glVertex3f(-0.2f, 0.0f, 0.8f);
        //}
        //glEnd();
        //glLineWidth(1.0f);
        //glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
		
		// Setup the quadric object
		pObj = gluNewQuadric();
		gluQuadricDrawStyle(pObj, GLU_FILL);
		gluQuadricNormals(pObj, GLU_SMOOTH);
		gluQuadricOrientation(pObj, GLU_OUTSIDE);
		gluQuadricTexture(pObj, GLU_FALSE);
		
		///////////////////////////////////////////////////////
		// Draw the blue Z axis first, with arrowed head
		glColor3f(0.0f, 0.0f, 1.0f);
		gluCylinder(pObj, fAxisRadius, fAxisRadius, fAxisHeight, 10, 1);
		glPushMatrix();
		glTranslatef(0.0f, 0.0f, 1.0f);
		gluCylinder(pObj, fArrowRadius, 0.0f, fArrowHeight, 10, 1);
		glRotatef(180.0f, 1.0f, 0.0f, 0.0f);
		gluDisk(pObj, fAxisRadius, fArrowRadius, 10, 1);
		glPopMatrix();
		
		///////////////////////////////////////////////////////
		// Draw the Red X axis 2nd, with arrowed head
		glColor3f(1.0f, 0.0f, 0.0f);
		glPushMatrix();
		glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
		gluCylinder(pObj, fAxisRadius, fAxisRadius, fAxisHeight, 10, 1);
		glPushMatrix();
		glTranslatef(0.0f, 0.0f, 1.0f);
		gluCylinder(pObj, fArrowRadius, 0.0f, fArrowHeight, 10, 1);
		glRotatef(180.0f, 0.0f, 1.0f, 0.0f);
		gluDisk(pObj, fAxisRadius, fArrowRadius, 10, 1);
		glPopMatrix();
		glPopMatrix();
		
		///////////////////////////////////////////////////////
		// Draw the Green Y axis 3rd, with arrowed head
		glColor3f(0.0f, 1.0f, 0.0f);
		glPushMatrix();
		glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);
		gluCylinder(pObj, fAxisRadius, fAxisRadius, fAxisHeight, 10, 1);
		glPushMatrix();
		glTranslatef(0.0f, 0.0f, 1.0f);
		gluCylinder(pObj, fArrowRadius, 0.0f, fArrowHeight, 10, 1);
		glRotatef(180.0f, 1.0f, 0.0f, 0.0f);
		gluDisk(pObj, fAxisRadius, fArrowRadius, 10, 1);
		glPopMatrix();
		glPopMatrix();
		
		////////////////////////////////////////////////////////
		// White Sphere at origin
		glColor3f(1.0f, 1.0f, 1.0f);
		gluSphere(pObj, 0.05f, 15, 15);
		
		// Delete the quadric
		gluDeleteQuadric(pObj);
    }
    glPopMatrix();

    if (was_blend) glEnable(GL_BLEND);
    if (was_texture) glEnable(GL_TEXTURE_2D);
    if (was_lighting) glEnable(GL_LIGHTING);
    if (was_fog) glEnable(GL_FOG);
    return;
}

/**
 * Draws a wired box.
 */
void g3dDrawWireBox(GLfloat lsize, GLfloat r, GLfloat g, GLfloat b) {
    glUseProgram(0);

    glPushMatrix();
    {
    	glLineWidth(lsize);
    	glBegin(GL_LINES);
    	{
            glColor3f(r, g, b);

            glVertex3f(-1.0f, -1.0f, 1.0f);
            glVertex3f(1.0f, -1.0f, 1.0f);

            glVertex3f(-1.0f, -1.0f, -1.0f);
            glVertex3f(1.0f, -1.0f, -1.0f);

            glVertex3f(-1.0f, 1.0f, 1.0f);
            glVertex3f(1.0f, 1.0f, 1.0f);

            glVertex3f(-1.0f, 1.0f, -1.0f);
            glVertex3f(1.0f, 1.0f, -1.0f);

            glVertex3f(-1.0f, -1.0f, 1.0f);
            glVertex3f(-1.0f, 1.0f, 1.0f);

            glVertex3f(-1.0f, -1.0f, -1.0f);
            glVertex3f(-1.0f, 1.0f, -1.0f);

            glVertex3f(1.0f, -1.0f, 1.0f);
            glVertex3f(1.0f, 1.0f, 1.0f);

            glVertex3f(1.0f, -1.0f, -1.0f);
            glVertex3f(1.0f, 1.0f, -1.0f);

            glVertex3f(-1.0f, -1.0f, 1.0f);
            glVertex3f(-1.0f, -1.0f, -1.0f);

            glVertex3f(1.0f, -1.0f, 1.0f);
            glVertex3f(1.0f, -1.0f, -1.0f);

            glVertex3f(-1.0f, 1.0f, 1.0f);
            glVertex3f(-1.0f, 1.0f, -1.0f);

            glVertex3f(1.0f, 1.0f, 1.0f);
            glVertex3f(1.0f, 1.0f, -1.0f);

            glColor3f(1, 1, 1);
    	}
    	glEnd();
    	glLineWidth(1.0f);
    }
    glPopMatrix();
	return;
}

/**
 * Draws a wired box.
 */
void g3dDrawWirePiramid(GLfloat lsize, GLfloat r, GLfloat g, GLfloat b) {
    glUseProgram(0);

    glPushMatrix();
    {
    	glLineWidth(lsize);
    	glBegin(GL_LINES);
    	{
            glColor3f(r, g, b);

            glVertex3f(0.0f, 0.0f, 0.0f);
            glVertex3f(-1.0f, 1.0f, -1.0f);

            glVertex3f(0.0f, 0.0f, 0.0f);
            glVertex3f(-1.0f, -1.0f, -1.0f);

            glVertex3f(0.0f, 0.0f, 0.0f);
            glVertex3f(1.0f, 1.0f, -1.0f);

            glVertex3f(0.0f, 0.0f, 0.0f);
            glVertex3f(1.0f, -1.0f, -1.0f);

            glVertex3f(-1.0f, 1.0f, -1.0f);
            glVertex3f(1.0f, 1.0f, -1.0f);

            glVertex3f(-1.0f, -1.0f, -1.0f);
            glVertex3f(1.0f, -1.0f, -1.0f);

            glVertex3f(1.0f, 1.0f, -1.0f);
            glVertex3f(1.0f, -1.0f, -1.0f);

            glVertex3f(-1.0f, 1.0f, -1.0f);
            glVertex3f(-1.0f, -1.0f, -1.0f);

            glColor3f(1, 1, 1);
    	}
    	glEnd();
    	glLineWidth(1.0f);
    }
    glPopMatrix();
	return;
}

/**
 * Draws a grid.
 */
void g3dDrawGrid(GLfloat fExtent, GLfloat fStep, GLfloat y, GLfloat r, GLfloat g, GLfloat b) {
    GLint iLine;

    MGPI_BOOL was_blend = MGPI_FALSE;
    MGPI_BOOL was_texture = MGPI_FALSE;
    MGPI_BOOL was_lighting = MGPI_FALSE;

    if (glIsEnabled(GL_BLEND)) {
        glDisable(GL_BLEND);
        was_blend = MGPI_TRUE;
    }

    if (glIsEnabled(GL_TEXTURE_2D)) {
        glDisable(GL_TEXTURE_2D);
        was_texture = MGPI_TRUE;
    }

    if (glIsEnabled(GL_LIGHTING)) {
        glDisable(GL_LIGHTING);
        was_lighting = MGPI_TRUE;
    }

    glUseProgram(0);

    glBegin(GL_LINES);
    {
        glColor3f(r, g, b);
        for (iLine = -fExtent; iLine <= fExtent; iLine += fStep) {
            glVertex3f(iLine, y, fExtent);
            glVertex3f(iLine, y, -fExtent);
            glVertex3f(fExtent, y, iLine);
            glVertex3f(-fExtent, y, iLine);
        }
        glColor3f(1.0f, 1.0f, 1.0f);
    }
    glEnd();

    if (was_blend) glEnable(GL_BLEND);
    if (was_texture) glEnable(GL_TEXTURE_2D);
    if (was_lighting) glEnable(GL_LIGHTING);
    return;
}

/**
 * Draw the texture on the quad given using the given program.
 */
void g3dDrawTexture2D(int texture, GLuint program, float2 scale) {
    MGPI_BOOL was_blend = MGPI_FALSE;
    MGPI_BOOL was_lighting = MGPI_FALSE;

    if (glIsEnabled(GL_BLEND)) {
        glDisable(GL_BLEND);
        was_blend = MGPI_TRUE;
    }

    if (glIsEnabled(GL_LIGHTING)) {
        glDisable(GL_LIGHTING);
        was_lighting = MGPI_TRUE;
    }

    if (glIsProgram(program)) glUseProgram(program);

    if (glIsTexture(texture)) glBindTexture(GL_TEXTURE_2D, texture);

    glPushMatrix();
    {
        glLoadIdentity();
        glMatrixMode(GL_PROJECTION);
        glPushMatrix();
        {
            glLoadIdentity();
            glOrtho(1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f);

            glMatrixMode(GL_MODELVIEW);

            glBegin(GL_QUADS);
			{
				glTexCoord2f(scale[X], 0);
				glVertex2f(1, -1);

				glTexCoord2f(scale[X], scale[Y]);
				glVertex2f(1, 1);

				glTexCoord2f(0, scale[Y]);
				glVertex2f(-1, 1);

				glTexCoord2f(0, 0);
				glVertex2f(-1, -1);
			}
			glEnd();

            glMatrixMode(GL_PROJECTION);
        }
        glPopMatrix();
        glMatrixMode(GL_MODELVIEW);
    }
    glPopMatrix();

    if (was_blend) glEnable(GL_BLEND);
    if (was_lighting) glEnable(GL_LIGHTING);
    return;
}

/**
 * Draws a standard teapot.
 */
void g3dDrawTeapot(float size, int texture, GLuint program) {

    if (glIsProgram(program)) glUseProgram(program);
    else glUseProgram(0);

    if (glIsTexture(texture)) glBindTexture(GL_TEXTURE_2D, texture);

    glFrontFace(GL_CW);
    glutSolidTeapot(size);
    glFrontFace(GL_CCW);

    return;
}

/**
 * Draws a standard 3D quad.
 */
void g3dDrawQuad(void) {
	glBegin(GL_QUADS);
	{
		glTexCoord2f(0, 0);
		glNormal3f(0, 0, 1);
		glVertex3f(-1, -1, 0);

		glTexCoord2f(1, 0);
		glNormal3f(0, 0, 1);
		glVertex3f(1, -1, 0);

		glTexCoord2f(1, 1);
		glNormal3f(0, 0, 1);
		glVertex3f(1, 1, 0);

		glTexCoord2f(0, 1);
		glNormal3f(0, 0, 1);
		glVertex3f(-1, 1, 0);
	}
	glEnd();
}

/**
 * Sets up a view matrix.
 */
void g3dLookAt(float4 position, float4 look_at, float4 up) {
    float4 F, UP, f, s = MGPI_DEFAULT_VECTOR, u = MGPI_DEFAULT_VECTOR;
    float lf, lu;

    F[X] = look_at[X] - position[X];
    F[Y] = look_at[Y] - position[Y];
    F[Z] = look_at[Z] - position[Z];

    lf = MGPI_VecLength(F);
    f[X] = F[X] / lf;
    f[Y] = F[Y] / lf;
    f[Z] = F[Z] / lf;

    lu = MGPI_VecLength(up);
    UP[X] = up[X] / lu;
    UP[Y] = up[Y] / lu;
    UP[Z] = up[Z] / lu;

    MGPI_CrossProduct(&s, f, UP);
    MGPI_CrossProduct(&u, s, f);

    s_aView[0][0] = s[X];
    s_aView[0][1] = s[Y];
    s_aView[0][2] = s[Z];
    s_aView[0][3] = 0.0f;
    s_aView[1][0] = u[X];
    s_aView[1][1] = u[Y];
    s_aView[1][2] = u[Z];
    s_aView[1][3] = 0.0f;
    s_aView[2][0] = -f[X];
    s_aView[2][1] = -f[Y];
    s_aView[2][2] = -f[Z];
    s_aView[2][3] = 0.0f;
    s_aView[3][0] = 0.0f;
    s_aView[3][1] = 0.0f;
    s_aView[3][2] = 0.0f;
    s_aView[3][3] = 1.0f;

    return;
}

/** ?????
 * Sets up a viewport transformation.
 */
void g3dViewport(float x, float y, float w, float h) {
    s_nXw = (s_nXnd + 1.0f) * (w / 2) + x;
    s_nYw = (s_nYnd + 1.0f) * (h / 2) + y;

    return;
}

/**
 * Sets up perspective projection matrix.
 */
void g3dPerspective(float fovy, float aspect, float near_clip_dist, float far_clip_dist) {
    float f = (cos(fovy / 2.0f) / sin(fovy / 2.0f));

    s_aProjection[0][0] = f / aspect;
    s_aProjection[0][1] = 0.0f;
    s_aProjection[0][2] = 0.0f;
    s_aProjection[0][3] = 0.0f;
    s_aProjection[1][0] = 0.0f;
    s_aProjection[1][1] = f;
    s_aProjection[1][2] = 0.0f;
    s_aProjection[1][3] = 0.0f;
    s_aProjection[2][0] = 0.0f;
    s_aProjection[2][1] = 0.0f;
    s_aProjection[2][2] = (far_clip_dist + near_clip_dist) / (near_clip_dist - far_clip_dist);
    s_aProjection[2][3] = 2 * far_clip_dist * near_clip_dist / (near_clip_dist - far_clip_dist);
    s_aProjection[3][0] = 0.0f;
    s_aProjection[3][1] = 0.0f;
    s_aProjection[3][2] = -1.0f;
    s_aProjection[3][3] = 0.0f;

    return;
}

/**
 * Sets up orthographic projection matrix.
 */
void g3dOrthographic(float near_clip_dist, float far_clip_dist, float left_clip_dist, float right_clip_dist, float bottom_clip_dist, float top_clip_dist) {
    float tx, ty, tz;
    tx = -((right_clip_dist + left_clip_dist) / (right_clip_dist - left_clip_dist));
    ty = -((top_clip_dist + bottom_clip_dist) / (top_clip_dist - bottom_clip_dist));
    tz = -((far_clip_dist + near_clip_dist) / (far_clip_dist - near_clip_dist));

    s_aProjection[0][0] = 2.0f / (right_clip_dist - left_clip_dist);
    s_aProjection[0][1] = 0.0f;
    s_aProjection[0][2] = 0.0f;
    s_aProjection[0][3] = tx;
    s_aProjection[1][0] = 0.0f;
    s_aProjection[1][1] = 2.0f / (top_clip_dist - bottom_clip_dist);
    s_aProjection[1][2] = 0.0f;
    s_aProjection[1][3] = ty;
    s_aProjection[2][0] = 0.0f;
    s_aProjection[2][1] = 0.0f;
    s_aProjection[2][2] = -2.0f / (far_clip_dist - near_clip_dist);
    s_aProjection[2][3] = tz;
    s_aProjection[3][0] = 0.0f;
    s_aProjection[3][1] = 0.0f;
    s_aProjection[3][2] = 0.0f;
    s_aProjection[3][3] = 1.0f;

    return;
}

/**
 * Multiplies the given input vector by the view and projection matrices.
 * The product is acquired as follows:
 * product = ((view * projection) * vector)
 */
void g3dViewProjection(float4 *product) {
    float4x4 viewProjection = MGPI_IDENTITY;

    MGPI_MatrixMul4(&viewProjection, s_aView, s_aProjection);
    MGPI_MatrixMulVect4(product, viewProjection, s_aVector);
    MGPI_Uniform4(product);

    return;
}

/**
 * Returns a world matrix in a form of a row oriented array.
 */
float *g3dGetWorldMatrix(void) {
    return MGPI_ToArray16(&s_aWorld);
}

/**
 * Returns a view matrix in a form of a row oriented array.
 */
float *g3dGetViewMatrix(void) {
    return MGPI_ToArray16(&s_aView);
}

/**
 * Returns a projection matrix in a form of a row oriented array.
 */
float *g3dGetProjectionMatrix(void) {
    return MGPI_ToArray16(&s_aProjection);
}
